include!(concat!(env!("OUT_DIR"), "/lunar_new_years.rs"));

const DAYS_OF_MONTH: [u16; 13] = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

const fn is_leap_year(year: u16) -> bool {
    year % 4 == 0 && year % 100 != 0 || year % 400 == 0
}

fn day_of_year(year: u16, month: u16, day: u16) -> u16 {
    day + DAYS_OF_MONTH.iter().take(month as usize).sum::<u16>()
        + if month > 2 && is_leap_year(year) {
            1
        } else {
            0
        }
}

fn remaining_days(year: u16, month: u16, day: u16) -> u16 {
    (if is_leap_year(year) { 366 } else { 365 }) - day_of_year(year, month, day)
}

/// Get day of week for the given solar new year.
///
/// 0 for Monday, 1 for Tuesday, etc.
const fn day_of_week_on_jan_1(year: u16) -> u16 {
    // A non-leap year has 365 days, which is (52 * 7 + 1) days.
    // A leap year has 366 days, which is (52 * 7 + 2) days.
    // January 1 is before the leap day, so we only count leap years up to the previous year.
    let prev_year = year - 1;
    let leap_count = prev_year / 4 - prev_year / 100 + prev_year / 400;

    // Having tested against recent years, this formula is correct.
    (prev_year + leap_count) % 7
}

fn week_of_year(year: u16, month: u16, day: u16) -> u16 {
    let day_of_week_year_start = day_of_week_on_jan_1(year);
    let day_of_week_year_end = day_of_week_on_jan_1(year + 1);

    let day_of_year = day_of_year(year, month, day);

    // A week with 4 or more days in the year belongs to the year.
    // Otherwise, it belongs to the adjacent year.
    match (day_of_week_year_start, day_of_week_year_end) {
        (..4, 4..) => {
            // Jan 1 is in week 1 of the year.
            // Dec 31 is in week 53 of the year.
            (day_of_year + day_of_week_year_start - 1) / 7 + 1
        }
        (..4, ..4) => {
            // Jan 1 is in week 1 of the year.
            // Dec 31 is in week 1 of the next year.
            // "Week 53" of the year is actually week 1 of the next year,
            // hence the previous formula also applies with a small adjustment.
            match (day_of_year + day_of_week_year_start - 1) / 7 + 1 {
                53 => 1,
                n => n,
            }
        }
        (4.., _) => {
            // Jan 1 is in week 52 or 53 of the previous year.
            // Dec 31 is in week 52 of the year.
            // If the day is before the first Monday, we use the previous year's formula.
            if day_of_year + day_of_week_year_start < 7 {
                week_of_year(year - 1, 12, day + 31)
            } else {
                (day_of_year + day_of_week_year_start - 1) / 7
            }
        }
    }
}

fn remaining_days_to_lunar_new_year(year: u16, month: u16, day: u16) -> u16 {
    let &(lny_month, lny_day) = LUNAR_NEW_YEARS.get(&year).unwrap();
    if (month, day) < (lny_month, lny_day) {
        day_of_year(year, lny_month, lny_day) - day_of_year(year, month, day) - 1
    } else {
        let &(lny_month, lny_day) = LUNAR_NEW_YEARS.get(&(year + 1)).unwrap();

        remaining_days(year, month, day) + day_of_year(year + 1, lny_month, lny_day) - 1
    }
}

pub fn time_info(time: &str) -> String {
    let mut fields = time.split('-');
    let year = fields.next().unwrap().parse().unwrap();
    let month = fields.next().unwrap().parse().unwrap();
    let day = fields.next().unwrap().parse().unwrap();

    format!(
        "{},{},{}",
        week_of_year(year, month, day),
        remaining_days(year, month, day),
        remaining_days_to_lunar_new_year(year, month, day)
    )
}
